import React, { FunctionComponent, useEffect, useRef, useMemo, memo, cloneElement, CSSProperties } from 'react';
import SortableJs from 'sortablejs';
import { insertNode, removeNode, deepCloneValue, filterObject, insertSortArray, saveObject, diffObject } from '../util';
import { useWatchEffect } from '../hooks';
import { saveAttrs, filterAttrs } from '../data';

export interface IGlobalWindow extends Window {
    dragItemParams?: Record<string, any>;
}

export interface IDragProps extends Omit<SortableJs.Options, 'dataIdAttr'> {
    /** 拖拽列表 */
    list: Array<Record<string, any>>;
    /** 更新拖拽列表回调 */
    setList: (newList: Array<any>) => void;
    /** 拖拽列表元素唯一标识 默认id */
    uniqueKey?: string;
    /** 子节点列表 */
    children: Array<React.ReactElement>;
    /** 拖拽开始事件 */
    onDragStart?: (e?: SortableJs.SortableEvent) => void;
    /** 拖拽过程中触发事件 */
    onDragMove?: (e?: SortableJs.MoveEvent) => void;
    /** 拖拽结束触发事件 */
    onDragEnd?: (e?: SortableJs.SortableEvent) => void;
    /** CSS样式Model */
    className?: any;
    /** 内联样式 */
    style?: CSSProperties;
}


const GlobalWindow: IGlobalWindow = window;

const DraggableNext: FunctionComponent<IDragProps> = (props) => {

    const { list, setList, onDragStart, onDragMove, onDragEnd, uniqueKey = "id", className, style, group, sort, delay, delayOnTouchOnly, animation, handle, filter, ghostClass, draggable, dragClass, chosenClass } = props;

    /** 拖拽实例 */
    const dragInstance = useRef<SortableJs | undefined>()
    /** 拖拽容器dom节点 */
    const dragRef = useRef<HTMLDivElement>(document.createElement('div'));
    /** 当前拖拽列表顺序（缓存） */
    const currDragList = useRef<Array<any>>([...list]);
    const currOption = useRef<Partial<IDragProps>>(saveObject(props, saveAttrs))

    /** 拖拽列表节点数组 */
    const draggableComponents = useMemo(() => {
        return (
            props.children.length ? props.children.map((node, index) => {
                return (cloneElement(node, { key: Reflect.get(list[index], uniqueKey) }))
            }) : null
        )
    }, [list]);

    /** 父组件手动修改列表的时候，同步更新拖拽组件内部列表缓存 */
    useEffect(() => {
        currDragList.current = [...list]
    }, [list])

    useEffect(() => {
        const options = filterObject(props, filterAttrs)
        /** 初始化拖拽组件实例 */
        const sortableInstance = SortableJs.create(dragRef.current, {
            ...options,
            onStart(e) {
                const { oldIndex } = e;
                if (typeof oldIndex === 'number') Reflect.set(GlobalWindow, 'dragItemParams', { ...deepCloneValue(currDragList.current[oldIndex]) })
                onDragStart?.(e)
            },
            onAdd(e) {
                const { from, to, newIndex } = e;
                if (from !== to) {
                    if (typeof newIndex === 'number' && GlobalWindow.dragItemParams) {
                        const newList = [...currDragList.current];
                        newList.splice(newIndex, 0, GlobalWindow.dragItemParams);
                        currDragList.current = [...newList]
                        removeNode(to, newIndex)
                        setList(newList)
                    }
                }
                props.onAdd?.(e)
            },
            onEnd(e) {
                const { from, to, oldIndex, newIndex } = e;
                if (from === to && typeof oldIndex === 'number' && typeof newIndex === 'number') {
                    let newList = insertSortArray([...currDragList.current], oldIndex, newIndex);
                    newList = newList.filter(op => op)
                    currDragList.current = [...newList]
                    setList(newList)
                }
                if (GlobalWindow.dragItemParams) Reflect.deleteProperty(GlobalWindow, 'dragItemParams')
                onDragEnd?.(e)
            },
            onRemove(e) {
                const { from, to, oldIndex, item } = e;
                if (from !== to && typeof oldIndex === 'number') {
                    const newList = [...currDragList.current];
                    newList.splice(oldIndex, 1);
                    currDragList.current = [...newList]
                    insertNode(from, oldIndex, item);
                    setList(newList)
                }
                props.onRemove?.(e)
            },
            onMove(e) {
                onDragMove?.(e)
            }
        })
        dragInstance.current = sortableInstance
        /** 组件销毁时，同步销毁拖拽实例 */
        return () => dragInstance.current?.destroy()
    }, [])

    useWatchEffect(() => {
        const deepChange = diffObject(currOption.current, saveObject(props, saveAttrs));
        if (Object.keys(deepChange).length) {
            Object.keys(deepChange).forEach((key: string) => {
                const keyName = key as keyof SortableJs.Options;
                const optionVal = Reflect.get(deepChange, keyName)
                if (dragInstance.current && optionVal) {
                    Reflect.set(currOption.current, keyName, optionVal)
                    dragInstance.current.option(keyName, optionVal)
                }
            })
        }
    }, [group, sort, delay, delayOnTouchOnly, animation, handle, filter, ghostClass, draggable, dragClass, chosenClass])


    return (
        <div style={style} className={className} ref={dragRef}>
            {draggableComponents}
        </div>
    )
}

export default memo(DraggableNext);